/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fourbox.server;

import fourbox.ProtocolMessage;
import fourbox.FUtil;
import fourbox.Metafile;
import fourbox.ServerProtocol;
import fourbox.client.Client;
import static fourbox.ServerProtocol.*;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author hartono
 */
public class ServerHandler implements Runnable {

    Thread serverDownloaderThread;
    List receivingQueue;
    ServerDownloader downloader;
    private Socket socket = null;
    private String username = "";
    private boolean loggedIn = false;
    private boolean wait = false;
    boolean receivingFile = false;
    double lastTime = 0;
    double currTime = 0;
    boolean waitUpload = false;
    private ServerProtocol protocol = null;
    private static final double TIMEOUT = 10000;
    Thread serverUploaderThread = null;
    ServerUploader sender = null;

    public ServerHandler(Socket socket) {
        this.socket = socket;
        System.out.println("Server : New Connection, creating new thread ...");
    }

    public ServerHandler(String username, Socket socket) {
        this.socket = socket;
        this.username = username;
        this.receivingQueue = Collections.synchronizedList(new ArrayList());;
        File repoDir = new File("Repository/tono");

        this.downloader = new ServerDownloader(receivingQueue, username);
        serverDownloaderThread = new Thread(this.downloader);


        System.out.println("Server : New Connection, creating new thread ...");


    }

    @Override
    public void run() {

        boolean connectionOn = true;
        try {

            System.out.println("Server : Thread " + username + " is started ");
//            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
//            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());

            while (connectionOn) {
                currTime = System.currentTimeMillis();
                // run until off                    
                if (inputStream.available() != 0) {
                    wait = false;
                    // ada input                                    
                    // get message length
                    int len = inputStream.readInt();
                    if (len == 0) {
                        break;
                    }
                    byte[] message = new byte[len];

                    // read message
                    inputStream.read(message);
//                    System.out.println("Server : Thread " + username + " message received : " + FUtil.byteArrayToString(message));

                    int resultCode = CONNECTION_DEAD;
                    // process message                              
                    if (loggedIn) {
                        // uda login
                        if (protocol != null) {
                            resultCode = protocol.processMessage(message);

                        }
                    } else {

                        // belum login
                        if ((protocol = serverAuthenticate(message)) == null) {
                            System.out.println("Server : Thread ,auntenticate failed");
                            resultCode = LOGIN_FAILED;
                        } else {
                            System.out.println("Server : Thread ,auntenticate success");
                            loggedIn = true;
                            resultCode = LOGIN_SUCCESS;
                            username = protocol.username;

                            this.receivingQueue = Collections.synchronizedList(new ArrayList());;
                            this.downloader = new ServerDownloader(receivingQueue, username);

                            serverDownloaderThread = new Thread(this.downloader);
                            serverDownloaderThread.start();

                            System.out.println("Server : Thread ,protocol is created");
                            lastTime = System.currentTimeMillis();
                        }
                    }
                    System.out.println("Server : Thread " + username + " message processing result : " + resultCode);

                    // processing result code
                    if (resultCode == CONNECTION_DEAD) {
                        connectionOn = false;
                        System.out.println("Server : Thread " + username + " should be closed");
                    } else if (resultCode == CONNECTION_ALIVE) {
                        connectionOn = true;
                        outputStream.writeInt(1);
                        outputStream.write(ProtocolMessage.SERVER_ACK_KEEP_ALIVE);
                        outputStream.flush();
                        lastTime = System.currentTimeMillis();
//                        System.out.println("Server : Thread " + username + " is still alive");
                    } else if (resultCode == LOGIN_SUCCESS) {
                        // create ack login true                    
                        // kirim
                        outputStream.writeInt(2); // len = 2 byte
                        outputStream.write(ProtocolMessage.SERVER_ACK_LOGIN); // 1 id ack login
                        outputStream.write(1); // 1 artinya true
                        outputStream.flush();
                        connectionOn = true;
                        loggedIn = true;
                        lastTime = System.currentTimeMillis();
                        System.out.println("Server : login success");
                    } else if (resultCode == LOGIN_FAILED) {
                        // create nak login / false
                        // kirim
                        outputStream.writeInt(2); // len = 2 byte
                        outputStream.write(ProtocolMessage.SERVER_ACK_LOGIN); // 1 id ack login
                        outputStream.write(0); // 1 artinya false
                        outputStream.flush();
                        connectionOn = false;
                        System.out.println("Server : login failed");
                    } else if (resultCode == ServerProtocol.RECEIVE_UPDATE_FROM_CLIENT) {
                        lastTime = System.currentTimeMillis();
                        int objectCount = message[1];
                        synchronized (receivingQueue) {
                            // queue penerimaan
                            for (int i = 0; i < objectCount; i++) {
                                Metafile m = null;
                                try {
                                    m = (Metafile) inputStream.readObject();
                                    System.out.println("Metafile to received " + m);
                                    receivingQueue.add(m);
                                    lastTime = System.currentTimeMillis();
                                } catch (ClassNotFoundException ex) {
                                }
                            }
                        }
                        lastTime = System.currentTimeMillis();
                    } else if (resultCode == ServerProtocol.RECEIVE_REGULAR_UPDATE_FROM_CLIENT) {
                        System.out.println("Server : checking regular update from client");
                        lastTime = System.currentTimeMillis();
                        int objectCount = message[1];
                        synchronized (receivingQueue) {
                            ArrayList<Metafile> clientFiles = new ArrayList<Metafile>();
                            for (int i = 0; i < objectCount; i++) {
                                Metafile m = null;
                                try {
                                    m = (Metafile) inputStream.readObject();
//                                    System.out.println("Client " + m);
//                                    receivingQueue.add(m);
                                    clientFiles.add(m);
                                    lastTime = System.currentTimeMillis();
                                } catch (ClassNotFoundException ex) {
                                }
                            }
                            File serverDir = new File("Repository/" + username);
                            ArrayList<Metafile> serverFiles = FUtil.getListFilesInServer(serverDir.toString(), serverDir.toPath());
                            for (Metafile m : serverFiles) {
//                                System.out.println("Server " + m);
                            }

                            // nge diff
                            // cek server, kalao ada yang lebih baru di server, krim message update beserta file yang berbeda
                            // upload ke client
                            // kalo sama, ya sudah,
                            // 
                            // cek , kalau ada yang lebih baru di client, kirim message request update / client ngpload file baru
                            // upload ke server                            
                            List sendingQueue = Collections.synchronizedList(new ArrayList());
                            synchronized (sendingQueue) {
                                // ngebandingin apakah server file ada di client atau gak
                                for (Metafile serverFile : serverFiles) {
                                    Metafile m = null;
                                    boolean found = false;
                                    for (Metafile clientFile : clientFiles) {
                                        if (serverFile.equals(clientFile)) {
                                            m = clientFile;
                                            found = true;
                                            break;
                                        }
                                    }
                                    if (found) {
                                        if (serverFile.lastModified > m.lastModified) {
                                            // yang di server lebih baru, maka harus dikirim ke client 
                                            sendingQueue.add(serverFile);
                                        } else if (serverFile.lastModified < m.lastModified) {
                                            receivingQueue.add(m);
                                        }
                                    } else {
                                        sendingQueue.add(serverFile);
                                    }
                                }

                                // ngebandingin apakah file di klien ada di server atau tidak
                                for (Metafile clientFile : clientFiles) {
                                    Metafile m = null;
                                    boolean found = false;
                                    for (Metafile serverFile : serverFiles) {
                                        if (clientFile.equals(serverFile)) {
                                            m = serverFile;
                                            found = true;
                                            break;
                                        }
                                    }
                                    if (found) {
//                                        if (clientFile.lastModified > m.lastModified) {
//                                            // yang di klien lebih baru, maka harus dikirim ke server
//                                            receivingQueue.add(m);
//                                        } else if (clientFile.lastModified < m.lastModified) {                                            
//                                            sendingQueue.add(clientFile);
//                                        }
                                    } else {
//                                        sendingQueue.add(clientFile);
                                        receivingQueue.add(clientFile);
                                    }
                                }//

                                // selesai membandingkan
                                if (sendingQueue.size() > 0 && !waitUpload) {
                                    System.out.println("Server : Will send " + sendingQueue.size() + " files to client");

//                                    if (sender == null) {
                                        
//                                    }

                                    outputStream.writeInt(2);
                                    outputStream.write(ProtocolMessage.SERVER_MSG_UPDATE);
                                    outputStream.write(sendingQueue.size());
                                    for (int i = 0; i < sendingQueue.size(); i++) {
                                        Metafile m = (Metafile) sendingQueue.get(i);
                                        System.out.println("Server : will send " + m);
                                        outputStream.writeObject(m);
                                    }
                                    outputStream.flush();

                                    try {
                                        Thread.sleep(1000);
                                    } catch (InterruptedException ex) {
                                        Logger.getLogger(ServerHandler.class.getName()).log(Level.SEVERE, null, ex);
                                    }

                                    sender = new ServerUploader(username, "localhost", Client.CLIENT_DOWNLOAD_PORT, sendingQueue);
                                    
                                    serverUploaderThread = new Thread(sender);
                                    System.out.println("Server : Created new sender thread , size = " + sender.queue.size());
                                    System.out.println("Server : Created new sender thread , queue size = " + sendingQueue.size());
                                    serverUploaderThread.start();

                                    waitUpload = true;
                                    lastTime = System.currentTimeMillis();
                                }

                                if (receivingQueue.size() > 0) {
                                    ProtocolMessage.createACKMsgListFilesRegular(receivingQueue, outputStream);
                                    receivingQueue.clear();
                                }

                                // 

                            }// sync


                        }
                        lastTime = System.currentTimeMillis();
                    }
                } else {
                    // ga ada input                    
                    if (!wait) {
                        // tidak wait
                        if (!loggedIn) {
                            // if belum logged in
                            // nunggun login
                            outputStream.writeInt(1);
                            outputStream.write(ProtocolMessage.SERVER_MSG_CONNECTED);
                            outputStream.flush();
                            System.out.println("Server : Thread sent msg , connected with client 1st time");
                            System.out.println("Server : Thread Waiting for client to login");
                            wait = true;
                            lastTime = System.currentTimeMillis();
                        } else {
                            // uda log in
                            // ngewait upload selesai
                            if (waitUpload) {
                                if (serverUploaderThread != null) {
                                    if (serverUploaderThread.isAlive()) {
                                        waitUpload = true;
                                    } else {
                                        waitUpload = false;
                                    }
                                }
                            }


                            // cek pake timer, apa yang terjadi, kalo ada sesuatu yang berubah, kirim message
                            // wait for ack = true;

                            if (currTime - lastTime > TIMEOUT) {
                                connectionOn = false;
                                System.out.println("Server : Thread TIME OUT while waiting for keep alive");
                            }


                        }
                    } else {
                        // wait for ack
                        if (loggedIn) {
                            if (currTime - lastTime > TIMEOUT) {
                                connectionOn = false;
                                System.out.println("Server : Thread TIME OUT while waiting for ack");
                            }
                        }
                    }

                }
            }
            socket.close();
            if (this.downloader != null) {
                this.downloader.listening = false;
                this.downloader.serverSocket.close();
            }
            System.out.println("Server : Thread " + username + " is closed");
        } catch (IOException ex) {
            Logger.getLogger(ServerHandler.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Server : Thread " + username + " error");
        }
    }
}
